import React from "react";
import propTypes from "prop-types";
import "./index.css";

class PuzzleReact extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            backgroundContext: null,
            slideContext: null,
            goalContext: null,

            offsetX: 0,
            offsetY: 0,

            isMousedown: false,
            mouseClientX: 0,

            slideOffsetX: 10,
            distance: 0,

            result: "default",
        };
    }
    componentDidMount() {
        this.initMoveWatcher();
        this.initCanvasContext();
        this.drawBackgroundImage();
    }
    componentWillUnmount() {
        this.removeMoveWatcher();
    }

    /**
     * @description 计算属性
     */
    goalOffsetComputed = () => ({
        left: `${this.state.offsetX}px`,
        top: `${this.state.offsetY}px`,
    });
    slideOffsetComputed = () => ({
        left: `${this.state.slideOffsetX + this.state.distance + 10}px`,
        top: `${this.state.offsetY}px`,
    });

    puzzleButtonComputed = () => ({
        left: `${this.state.distance}px`,
    });

    puzzleResultClassComputed = () => {
        const classes = [
            "puzzle-result",
            (this.state.result == "success" || this.state.result == "fail") &&
                `puzzle-result-${this.state.result}`,
            
        ]
        return classes.join(" ");
    };

    puzzleResultTextComputed = () => {
        const TEXT_ENMU = {
            success: this.props.successText,
            fail: this.props.failText,
        };
        return TEXT_ENMU[this.state.result];
    };

    /**
     * @methods
     */
    initMoveWatcher = () => {
        const body = document.body;
        body.addEventListener("mouseleave", this.onMouseLeave);
        body.addEventListener("mousemove", this.onMouseMove);
        body.addEventListener("mouseup", this.onMouseUp);
    };

    removeMoveWatcher = () => {
        const body = document.body;
        body.removeEventListener("mouseleave", this.onMouseLeave);
        body.removeEventListener("mousemove", this.onMouseMove);
        body.removeEventListener("mouseup", this.onMouseUp);
    };

    initCanvasContext = () => {
        this.setState({
            backgroundContext: this.getCanvasContext("#puzzle-background"),
            slideContext: this.getCanvasContext("#puzzle-slide"),
            goalContext: this.getCanvasContext("#puzzle-goal"),
        });
    };

    getCanvasContext(selector) {
        const canvas = document.querySelector(selector);
        return canvas.getContext("2d");
    }

    drawBackgroundImage = async () => {
        const img = await this.getImage();
        if (img) {
            const scale = this.props.width / this.props.height;
            this.state.backgroundContext.drawImage(
                img,
                0,
                0,
                img.width,
                img.width / scale,
                0,
                0,
                this.props.width,
                this.props.height
            );
            this.drawSlideImage();
        }
    };

    getImage() {
        return new Promise((resolve) => {
            const img = document.getElementById("puzzle-img");
            img.onload = () => {
                resolve(img || false);
            };
        });
    }

    getBackgroundRandomArea = () => {
        const x = ~~(Math.random() * 80 + 140);
        const y = ~~((this.props.height - 50) / 2);
        const imgData = this.state.backgroundContext.getImageData(x, y, 50, 50);
        // this.state.offsetX = 10 + x;
        // this.state.offsetY = 10 + y;
        this.setState({
            offsetX: 10 + x,
            offsetY: 10 + y,
        });
        return imgData;
    };

    drawSlideImage = () => {
        const imgData = this.getBackgroundRandomArea();
        this.state.slideContext.putImageData(imgData, 0, 0);
    };

    onMouseDown = ({ clientX }) => {
        this.setState({
            isMousedown: true,
            mouseClientX: clientX,
        });
    };

    onMouseUp = () => {
        this.resultVerify();
        this.setState({
            isMousedown: false,
            mouseClientX: null,
        });
    };

    onMouseMove = ({ clientX }) => {
        if (this.state.isMousedown) {
            if (clientX - this.state.mouseClientX > 0) {
                this.setState({
                    distance: clientX - this.state.mouseClientX,
                });
            }
        }
    };

    onMouseLeave = () => {
        this.onMouseUp();
    };

    /**
     * @description
     * GAP 代表SLIDE 距离 GOAL 的偏差,其中 offsetX是生成GOAL的style left距离,
     * 注意：是距离最外层父元素的距离而不是BACKGROUND的距离,减去10是因为BACKGROUND
     * 距离最外层父元素有10间距偏差,然后this.slideOffsetX + this.distance是SLIDE
     * 真正距离BACKGROUND的距离,因为拖动按钮是从BACKGROUND对齐的.
     * 再次设置isMousedown = false,是因为校验完成之后不允许再次拖动,应该手动调用reset方法恢复出厂设置重新进行验证;
     */

    resultVerify = () => {
        var { isMousedown, offsetX, distance, result, slideOffsetX } =
            this.state;
        if (isMousedown) {
            const GAP = Math.abs(offsetX - 10 - (slideOffsetX + distance));
            result = GAP <= this.props.offsetDistance ? "success" : "fail";
            isMousedown = false;
            if (this.props.errorRetry && result == "fail") {
                setTimeout(this.reset, 1000);
            }
            this.props.complete && this.props.complete(this.state.result);

            this.setState({
                isMousedown,
                offsetX,
                distance,
                result,
            });
        }
    };

    reset = () => {
        this.resetState();
        this.drawSlideImage();
        this.drawBackgroundImage();
    };
    resetState = () => {
        this.setState({
            isMousedown: false,
            mouseClientX: 0,
            distance: 0,
            result: "default",
        });
    };

    render() {
        const { img, width, height } = this.props;
        return (
            <div className="puzzle">
                <img crossOrigin="" hidden src={img} alt="" id="puzzle-img" />
                <canvas
                    id="puzzle-background"
                    width={width}
                    height={height}
                    style={{ background: "transparent" }}
                ></canvas>
                <canvas
                    style={this.slideOffsetComputed()}
                    width="50"
                    height="50"
                    id="puzzle-slide"
                ></canvas>
                <canvas
                    style={this.goalOffsetComputed()}
                    width="50"
                    height="50"
                    id="puzzle-goal"
                ></canvas>

                <div className="puzzle-box">
                    <div className="puzzle-handle">
                        <div
                            style={this.puzzleButtonComputed()}
                            onMouseDown={this.onMouseDown}
                            className="puzzle-button"
                        >
                            <svg
                                xmlns="http://www.w3.org/2000/svg"
                                viewBox="0 0 24 24"
                            >
                                <path
                                    d="M10 17l5-5l-5-5v10z"
                                    fill="currentColor"
                                ></path>
                            </svg>
                        </div>
                    </div>
                </div>

                <div className={this.puzzleResultClassComputed()}>
                    {this.puzzleResultTextComputed()}
                </div>
                <div className="puzzle-refresh" onClick={this.reset}>
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                        <path
                            d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"
                            fill="currentColor"
                        ></path>
                    </svg>
                </div>
            </div>
        );
    }
}

export default PuzzleReact;

PuzzleReact.propTypes = {
    width: propTypes.number,
    height: propTypes.number,
    offsetDistance: propTypes.number,
    successText: propTypes.string,
    failText: propTypes.string,
    img: propTypes.string,
    errorRetry: propTypes.bool,
    complete: propTypes.func,
};

PuzzleReact.defaultProps = {
    width: 300,
    height: 150,
    offsetDistance: 10,
    successText: "校验成功",
    failText: "校验失败",
    img: "https://i.loli.net/2021/08/30/lj1ciV3An8JDo6u.jpg",
    errorRetry: true,
};
